Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assume Content-Type is json for endpoints that require json #4575

Merged
merged 15 commits into from
Jan 31, 2024

Conversation

eserilev
Copy link
Collaborator

@eserilev eserilev commented Aug 7, 2023

Issue Addressed

#4568

Proposed Changes

Create a custom filter that parses json in the request body by default without checking the content-type header. Apply the filter to endpoints that expect JSON in the request body.

Additional Info

The built in warp json filter warp::body::json defaults to Content-Type application/json if a Content-Type isn't provided. If a Content-Type is provided and it is not application/json the request will fail.

I ran several tests on my own, and was unable to recreate a failed endpoint request when Content-Type was omitted. I was however able to recreate a failed endpoint request when Content-Type was included but wasn't application/json.


In out test suite note that builder.json() appends Content-Type application/json to the header by default.

I created a new function post_generic_json_without_content_type_header which creates a post request with serialized json in the body without including Content-Type application/json in the header.

Locally I replaced all post requests in our test suite to NOT include the Content-Type header and all the tests passed. In this PR I only updated a single test to send a POST request without the Content-Type header.

I plan on also spinning up the client locally and making some curl requests myself to make sure I haven't missed anything.

@eserilev
Copy link
Collaborator Author

eserilev commented Aug 10, 2023

based on @michaelsproul work in #4595 it seems the GET validator/block endpoint fails when a content-type isn't provided

the weird part here is that our test suite doesn't provide a content type header when making this request. furthermore, based on the warp filters for this request I dont see a place where we even require a content-type header. Will investigate this further

EDIT: nevermind!

@michaelsproul
Copy link
Member

@eserilev I don't think any of our GETs fail due to lack of Content-Type. In my PR I was referring to POST /eth/v2/beacon/blocks

bors bot pushed a commit that referenced this pull request Aug 14, 2023
## Issue Addressed

Closes #3404 (mostly)

## Proposed Changes

- Remove all uses of Warp's `and_then` (which backtracks) in favour of `then` (which doesn't).
- Bump the priority of the `POST` method for `v2/blocks` to `P0`. Publishing a block needs to happen quickly.
- Run the new SSZ POST endpoints on the beacon processor. I think this was missed in between merging #4462 and #4504/#4479.
- Fix a minor issue in the validator registrations endpoint whereby an error from spawning the task on the beacon processor would be dropped.

## Additional Info

I've tested this manually and can confirm that we no longer get the dreaded `Unsupported endpoint version` errors for queries like:

```
$ curl -X POST -H "Content-Type: application/json" --data @block.json "http://localhost:5052/eth/v2/beacon/blocks" | jq
{
  "code": 400,
  "message": "BAD_REQUEST: WeakSubjectivityConflict",
  "stacktraces": []
}
```

```
$ curl -X POST -H "Content-Type: application/octet-stream" --data @block.json "http://localhost:5052/eth/v2/beacon/blocks" | jq
{
  "code": 400,
  "message": "BAD_REQUEST: invalid SSZ: OffsetOutOfBounds(572530811)",
  "stacktraces": []
}
```

```
$ curl "http://localhost:5052/eth/v2/validator/blocks/7067595"
{"code":400,"message":"BAD_REQUEST: invalid query: Invalid query string","stacktraces":[]}
```

However, I can still trigger it by leaving off the `Content-Type`. We can re-test this aspect with #4575.
bors bot pushed a commit that referenced this pull request Aug 14, 2023
## Issue Addressed

Closes #3404 (mostly)

## Proposed Changes

- Remove all uses of Warp's `and_then` (which backtracks) in favour of `then` (which doesn't).
- Bump the priority of the `POST` method for `v2/blocks` to `P0`. Publishing a block needs to happen quickly.
- Run the new SSZ POST endpoints on the beacon processor. I think this was missed in between merging #4462 and #4504/#4479.
- Fix a minor issue in the validator registrations endpoint whereby an error from spawning the task on the beacon processor would be dropped.

## Additional Info

I've tested this manually and can confirm that we no longer get the dreaded `Unsupported endpoint version` errors for queries like:

```
$ curl -X POST -H "Content-Type: application/json" --data @block.json "http://localhost:5052/eth/v2/beacon/blocks" | jq
{
  "code": 400,
  "message": "BAD_REQUEST: WeakSubjectivityConflict",
  "stacktraces": []
}
```

```
$ curl -X POST -H "Content-Type: application/octet-stream" --data @block.json "http://localhost:5052/eth/v2/beacon/blocks" | jq
{
  "code": 400,
  "message": "BAD_REQUEST: invalid SSZ: OffsetOutOfBounds(572530811)",
  "stacktraces": []
}
```

```
$ curl "http://localhost:5052/eth/v2/validator/blocks/7067595"
{"code":400,"message":"BAD_REQUEST: invalid query: Invalid query string","stacktraces":[]}
```

However, I can still trigger it by leaving off the `Content-Type`. We can re-test this aspect with #4575.
@eserilev
Copy link
Collaborator Author

eserilev commented Aug 15, 2023

@michaelsproul I did some digging in the curl manual, apparently curl defaults to Content-Type application/x-www-form-urlencoded if no content-type header is provided.

Like -d, --data the default content-type sent to the server is application/x-www-form-urlencoded

https://curl.se/docs/manpage.html

As i mentioned above, the default warp:body:json filter will always default to Content-Type application/json unless a different content type is provided. If a different content type is provided the request will fail.

I believe my changes here should effectively ignore the content-type header and resolve issues users have when making curl requests to relevant endpoints.

@eserilev
Copy link
Collaborator Author

eserilev commented Aug 15, 2023

By the way, per the HTTP spec, I think ignoring/overriding the content-type header in this situation is ok

In practice, resource owners do not always properly configure their origin server to provide the correct Content-Type for a given representation. Some user agents examine the content and, in certain cases, override the received type (for example, see [Sniffing])

https://www.rfc-editor.org/rfc/rfc9110.html#name-content-type

@chong-he
Copy link
Member

chong-he commented Dec 19, 2023

I have tested this and for queries without -H 'Content-Type: application/json', the queries work as intended.

Details:

With Lighthouse v4.3.0-9a24a01, querying:

curl -X 'POST'   'http://localhost:5052/eth/v1/beacon/rewards/sync_committee/head'   -H 'accept: application/json'  -d '["502264"]' | jq

returns:

{
  "execution_optimistic": true,
  "finalized": false,
  "data": []
}

With Lighthouse v4.5.0-441fc16, the same query returns:

{
  "code": 400,
  "message": "BAD_REQUEST: Unsupported endpoint version: v1",
  "stacktraces": []
}

If this is good we can merge this PR?

Copy link
Member

@michaelsproul michaelsproul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, would be nice to merge this for 4.6.0-rc

Looks like there's a cargo audit failure that could probably be fixed by cargo update -p zerocopy.

common/eth2/src/lib.rs Outdated Show resolved Hide resolved
common/warp_utils/src/json.rs Outdated Show resolved Hide resolved
@chong-he
Copy link
Member

Did a testing on passing an invalid data.

With Lighthouse v4.5.0-3e8f176`, querying:
(note the validator passed is an alphabet to provide invalid data)

curl -X 'POST'   'http://localhost:5052/eth/v1/beacon/rewards/sync_committee/head'   -H 'accept: application/json'  -d '[“a"]' | jq

returns:

{
  "code": 400,
  "message": "BAD_REQUEST: Error(\"a cannot be parsed as a slot: invalid digit found in string\", line: 3, column: 1)",
  "stacktraces": []
}

With Lighthouse v4.5.0-441fc16, the same query returns:

{
  "code": 400,
  "message": "BAD_REQUEST: Unsupported endpoint version: v1",
  "stacktraces": []
}

The updated code is able to show the correct error message when an invalid data is passed, which is good.

Woodpile37 pushed a commit to Woodpile37/lighthouse that referenced this pull request Jan 6, 2024
Closes sigp#3404 (mostly)

- Remove all uses of Warp's `and_then` (which backtracks) in favour of `then` (which doesn't).
- Bump the priority of the `POST` method for `v2/blocks` to `P0`. Publishing a block needs to happen quickly.
- Run the new SSZ POST endpoints on the beacon processor. I think this was missed in between merging sigp#4462 and sigp#4504/sigp#4479.
- Fix a minor issue in the validator registrations endpoint whereby an error from spawning the task on the beacon processor would be dropped.

I've tested this manually and can confirm that we no longer get the dreaded `Unsupported endpoint version` errors for queries like:

```
$ curl -X POST -H "Content-Type: application/json" --data @block.json "http://localhost:5052/eth/v2/beacon/blocks" | jq
{
  "code": 400,
  "message": "BAD_REQUEST: WeakSubjectivityConflict",
  "stacktraces": []
}
```

```
$ curl -X POST -H "Content-Type: application/octet-stream" --data @block.json "http://localhost:5052/eth/v2/beacon/blocks" | jq
{
  "code": 400,
  "message": "BAD_REQUEST: invalid SSZ: OffsetOutOfBounds(572530811)",
  "stacktraces": []
}
```

```
$ curl "http://localhost:5052/eth/v2/validator/blocks/7067595"
{"code":400,"message":"BAD_REQUEST: invalid query: Invalid query string","stacktraces":[]}
```

However, I can still trigger it by leaving off the `Content-Type`. We can re-test this aspect with sigp#4575.
Woodpile37 pushed a commit to Woodpile37/lighthouse that referenced this pull request Jan 6, 2024
Closes sigp#3404 (mostly)

- Remove all uses of Warp's `and_then` (which backtracks) in favour of `then` (which doesn't).
- Bump the priority of the `POST` method for `v2/blocks` to `P0`. Publishing a block needs to happen quickly.
- Run the new SSZ POST endpoints on the beacon processor. I think this was missed in between merging sigp#4462 and sigp#4504/sigp#4479.
- Fix a minor issue in the validator registrations endpoint whereby an error from spawning the task on the beacon processor would be dropped.

I've tested this manually and can confirm that we no longer get the dreaded `Unsupported endpoint version` errors for queries like:

```
$ curl -X POST -H "Content-Type: application/json" --data @block.json "http://localhost:5052/eth/v2/beacon/blocks" | jq
{
  "code": 400,
  "message": "BAD_REQUEST: WeakSubjectivityConflict",
  "stacktraces": []
}
```

```
$ curl -X POST -H "Content-Type: application/octet-stream" --data @block.json "http://localhost:5052/eth/v2/beacon/blocks" | jq
{
  "code": 400,
  "message": "BAD_REQUEST: invalid SSZ: OffsetOutOfBounds(572530811)",
  "stacktraces": []
}
```

```
$ curl "http://localhost:5052/eth/v2/validator/blocks/7067595"
{"code":400,"message":"BAD_REQUEST: invalid query: Invalid query string","stacktraces":[]}
```

However, I can still trigger it by leaving off the `Content-Type`. We can re-test this aspect with sigp#4575.
warp::body::bytes().and_then(|bytes: Bytes| async move {
Json::decode(bytes).map_err(|err| reject::custom_bad_request(format!("{:?}", err)))
})
}
Copy link
Collaborator

@dapplion dapplion Jan 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diff from warp::filters::body::json

pub fn json<T: DeserializeOwned + Send>() -> impl Filter<Extract = (T,), Error = Rejection> + Copy {
-    is_content_type::<Json>()
        .and(bytes())
        .and_then(|buf| async move {
-           Json::decode(buf).map_err(|err| {
-               tracing::debug!("request json body error: {}", err);
-               reject::known(BodyDeserializeError { cause: err })
-           })
+           Json::decode(bytes).map_err(|err| reject::custom_bad_request(format!("{:?}", err)))
        })
}
impl Decode for Json {
-    fn decode<B: Buf, T: DeserializeOwned>(mut buf: B) -> Result<T, BoxError> {
+    fn decode<T: DeserializeOwned>(bytes: Bytes) -> Result<T, BoxError> {
-        serde_json::from_slice(&buf.copy_to_bytes(buf.remaining())).map_err(Into::into)
+        serde_json::from_slice(&bytes).map_err(Into::into)
    }
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Equivalent code with the suggestion to remove the copy from #4575 (comment)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eserilev consider appending some extra message context like

} else if let Some(e) = err.find::<warp::filters::body::BodyDeserializeError>() {
message = format!("BAD_REQUEST: body deserialize error: {}", e);

Copy link
Collaborator

@dapplion dapplion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR should be good to go once conflicts fixed

Does it slightly improve #5104 as there's one less filter?

Comment on lines 86 to 92
pub struct CustomDeserializeError(pub String);

impl Reject for CustomBadRequest {}

pub fn custom_deserialize_error(msg: String) -> warp::reject::Rejection {
warp::reject::custom(CustomDeserializeError(msg))
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added some extra message context

Comment on lines 173 to 175
} else if let Some(e) = err.find::<crate::reject::CustomDeserializeError>() {
message = format!("BAD_REQUEST: body deserialize error: {}", e);
code = StatusCode::BAD_REQUEST;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added some extra message context

@eserilev
Copy link
Collaborator Author

eserilev commented Jan 29, 2024

conflicts resolved and added additional error message context

Does it slightly improve #5104 as there's one less filter?

From what I understand #5104 is caused by nested futures. Since the json filter still makes an async call, I have a feeling it doesnt make any improvements. Tbh I dont know enough to give a definite answer though

Copy link
Collaborator

@dapplion dapplion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the revisions!

@michaelsproul michaelsproul added ready-for-merge This PR is ready to merge. and removed ready-for-review The code is ready for review labels Jan 30, 2024
@michaelsproul
Copy link
Member

@Mergifyio queue

Copy link

mergify bot commented Jan 30, 2024

queue

🛑 The pull request has been removed from the queue default

The queue conditions cannot be satisfied due to failing checks.

You can take a look at Queue: Embarked in merge queue check runs for more details.

In case of a failure due to a flaky test, you should first retrigger the CI.
Then, re-embark the pull request into the merge queue by posting the comment
@mergifyio refresh on the pull request.

@michaelsproul
Copy link
Member

@Mergifyio requeue

Copy link

mergify bot commented Jan 31, 2024

requeue

✅ This pull request will be re-embarked automatically

The followup queue command will be automatically executed to re-embark the pull request

Copy link

mergify bot commented Jan 31, 2024

queue

✅ The pull request has been merged automatically

The pull request has been merged automatically at 1d87edb

mergify bot added a commit that referenced this pull request Jan 31, 2024
@mergify mergify bot merged commit 1d87edb into sigp:unstable Jan 31, 2024
29 checks passed
danielrachi1 pushed a commit to danielrachi1/lighthouse that referenced this pull request Feb 14, 2024
* added default content type filter

* Merge branch 'unstable' of https://github.com/sigp/lighthouse into unstable

* create custom warp json filter that ignores content type header

* cargo fmt and linting

* updated test

* updated test

* merge unstable

* merge conflicts

* workspace=true

* use Bytes instead of Buf

* resolve merge conflict

* resolve merge conflicts

* add extra error message context

* merge conflicts

* lint
@chong-he chong-he added the v5.1.0 Q2 2024 label Mar 7, 2024
@realbigsean realbigsean mentioned this pull request Jul 11, 2024
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
HTTP-API ready-for-merge This PR is ready to merge. v5.1.0 Q2 2024
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants